/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <assert.h>
#include <io.h>
#include <stdio.h>
#include <stdlib.h>

#include "uv.h"
#include "internal.h"
#include "handle-inl.h"

/*
 * The `child_stdio_buffer` buffer has the following layout:
 *   int number_of_fds
 *   unsigned char crt_flags[number_of_fds]
 *   HANDLE os_handle[number_of_fds]
 */
#define CHILD_STDIO_SIZE(count) \
    (sizeof(int) + sizeof(unsigned char) * (count) + sizeof(uintptr_t) * (count))

#define CHILD_STDIO_COUNT(buffer) \
    *((unsigned int*)(buffer))

#define CHILD_STDIO_CRT_FLAGS(buffer, fd) \
    *((unsigned char*)(buffer) + sizeof(int) + fd)

#define CHILD_STDIO_HANDLE(buffer, fd) \
    *((HANDLE*)((unsigned char*)(buffer) + sizeof(int) + sizeof(unsigned char) * CHILD_STDIO_COUNT((buffer)) + sizeof(HANDLE) * (fd)))

/* CRT file descriptor mode flags */
#define FOPEN 0x01
#define FEOFLAG 0x02
#define FCRLF 0x04
#define FPIPE 0x08
#define FNOINHERIT 0x10
#define FAPPEND 0x20
#define FDEV 0x40
#define FTEXT 0x80

/*
 * Clear the HANDLE_FLAG_INHERIT flag from all HANDLEs that were inherited
 * the parent process. Don't check for errors - the stdio handles may not be
 * valid, or may be closed already. There is no guarantee that this function
 * does a perfect job.
 */
void uv_disable_stdio_inheritance(void)
{
    HANDLE handle;
    STARTUPINFOW si;

    /* Make the windows stdio handles non-inheritable. */
    handle = GetStdHandle(STD_INPUT_HANDLE);
    if (handle != NULL && handle != INVALID_HANDLE_VALUE)
        SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);

    handle = GetStdHandle(STD_OUTPUT_HANDLE);
    if (handle != NULL && handle != INVALID_HANDLE_VALUE)
        SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);

    handle = GetStdHandle(STD_ERROR_HANDLE);
    if (handle != NULL && handle != INVALID_HANDLE_VALUE)
        SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);

    /* Make inherited CRT FDs non-inheritable. */
    GetStartupInfoW(&si);
    if (uv__stdio_verify(si.lpReserved2, si.cbReserved2))
        uv__stdio_noinherit(si.lpReserved2);
}

static int uv__create_stdio_pipe_pair(uv_loop_t* loop,
    uv_pipe_t* server_pipe, HANDLE* child_pipe_ptr, unsigned int flags)
{
    char pipe_name[64];
    SECURITY_ATTRIBUTES sa;
    DWORD server_access = 0;
    DWORD client_access = 0;
    HANDLE child_pipe = INVALID_HANDLE_VALUE;
    int err;

    if (flags & UV_READABLE_PIPE) {
        /* The server needs inbound access too, otherwise CreateNamedPipe() */
        /* won't give us the FILE_READ_ATTRIBUTES permission. We need that to */
        /* probe the state of the write buffer when we're trying to shutdown */
        /* the pipe. */
        server_access |= PIPE_ACCESS_OUTBOUND | PIPE_ACCESS_INBOUND;
        client_access |= GENERIC_READ | FILE_WRITE_ATTRIBUTES;
    }
    if (flags & UV_WRITABLE_PIPE) {
        server_access |= PIPE_ACCESS_INBOUND;
        client_access |= GENERIC_WRITE | FILE_READ_ATTRIBUTES;
    }

    /* Create server pipe handle. */
    err = uv_stdio_pipe_server(loop,
        server_pipe,
        server_access,
        pipe_name,
        sizeof(pipe_name));
    if (err)
        goto error;

    /* Create child pipe handle. */
    sa.nLength = sizeof sa;
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    child_pipe = CreateFileA(pipe_name,
        client_access,
        0,
        &sa,
        OPEN_EXISTING,
        server_pipe->ipc ? FILE_FLAG_OVERLAPPED : 0,
        NULL);
    if (child_pipe == INVALID_HANDLE_VALUE) {
        err = GetLastError();
        goto error;
    }

#ifndef NDEBUG
    /* Validate that the pipe was opened in the right mode. */
    {
        DWORD mode;
        BOOL r = GetNamedPipeHandleState(child_pipe,
            &mode,
            NULL,
            NULL,
            NULL,
            NULL,
            0);
        assert(r == TRUE);
        assert(mode == (PIPE_READMODE_BYTE | PIPE_WAIT));
    }
#endif

    /* Do a blocking ConnectNamedPipe.  This should not block because we have */
    /* both ends of the pipe created. */
    if (!ConnectNamedPipe(server_pipe->handle, NULL)) {
        if (GetLastError() != ERROR_PIPE_CONNECTED) {
            err = GetLastError();
            goto error;
        }
    }

    /* The server end is now readable and/or writable. */
    if (flags & UV_READABLE_PIPE)
        server_pipe->flags |= UV_HANDLE_WRITABLE;
    if (flags & UV_WRITABLE_PIPE)
        server_pipe->flags |= UV_HANDLE_READABLE;

    *child_pipe_ptr = child_pipe;
    return 0;

error:
    if (server_pipe->handle != INVALID_HANDLE_VALUE) {
        uv_pipe_cleanup(loop, server_pipe);
    }

    if (child_pipe != INVALID_HANDLE_VALUE) {
        CloseHandle(child_pipe);
    }

    return err;
}

static int uv__duplicate_handle(uv_loop_t* loop, HANDLE handle, HANDLE* dup)
{
    HANDLE current_process;

    /* _get_osfhandle will sometimes return -2 in case of an error. This seems */
    /* to happen when fd <= 2 and the process' corresponding stdio handle is */
    /* set to NULL. Unfortunately DuplicateHandle will happily duplicate */
    /* (HANDLE) -2, so this situation goes unnoticed until someone tries to */
    /* use the duplicate. Therefore we filter out known-invalid handles here. */
    if (handle == INVALID_HANDLE_VALUE || handle == NULL || handle == (HANDLE)-2) {
        *dup = INVALID_HANDLE_VALUE;
        return ERROR_INVALID_HANDLE;
    }

    current_process = GetCurrentProcess();

    if (!DuplicateHandle(current_process,
            handle,
            current_process,
            dup,
            0,
            TRUE,
            DUPLICATE_SAME_ACCESS)) {
        *dup = INVALID_HANDLE_VALUE;
        return GetLastError();
    }

    return 0;
}

static int uv__duplicate_fd(uv_loop_t* loop, int fd, HANDLE* dup)
{
    HANDLE handle;

    if (fd == -1) {
        *dup = INVALID_HANDLE_VALUE;
        return ERROR_INVALID_HANDLE;
    }

    handle = uv__get_osfhandle(fd);
    return uv__duplicate_handle(loop, handle, dup);
}

int uv__create_nul_handle(HANDLE* handle_ptr,
    DWORD access)
{
    HANDLE handle;
    SECURITY_ATTRIBUTES sa;

    sa.nLength = sizeof sa;
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    handle = CreateFileW(L"NUL",
        access,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        &sa,
        OPEN_EXISTING,
        0,
        NULL);
    if (handle == INVALID_HANDLE_VALUE) {
        return GetLastError();
    }

    *handle_ptr = handle;
    return 0;
}

int uv__stdio_create(uv_loop_t* loop,
    const uv_process_options_t* options,
    BYTE** buffer_ptr)
{
    BYTE* buffer;
    int count, i;
    int err;

    count = options->stdio_count;

    if (count < 0 || count > 255) {
        /* Only support FDs 0-255 */
        return ERROR_NOT_SUPPORTED;
    } else if (count < 3) {
        /* There should always be at least 3 stdio handles. */
        count = 3;
    }

    /* Allocate the child stdio buffer */
    buffer = (BYTE*)uv__malloc(CHILD_STDIO_SIZE(count));
    if (buffer == NULL) {
        return ERROR_OUTOFMEMORY;
    }

    /* Prepopulate the buffer with INVALID_HANDLE_VALUE handles so we can */
    /* clean up on failure. */
    CHILD_STDIO_COUNT(buffer) = count;
    for (i = 0; i < count; i++) {
        CHILD_STDIO_CRT_FLAGS(buffer, i) = 0;
        CHILD_STDIO_HANDLE(buffer, i) = INVALID_HANDLE_VALUE;
    }

    for (i = 0; i < count; i++) {
        uv_stdio_container_t fdopt;
        if (i < options->stdio_count) {
            fdopt = options->stdio[i];
        } else {
            fdopt.flags = UV_IGNORE;
        }

        switch (fdopt.flags & (UV_IGNORE | UV_CREATE_PIPE | UV_INHERIT_FD | UV_INHERIT_STREAM)) {
        case UV_IGNORE:
            /* Starting a process with no stdin/stout/stderr can confuse it. */
            /* So no matter what the user specified, we make sure the first */
            /* three FDs are always open in their typical modes, e.g. stdin */
            /* be readable and stdout/err should be writable. For FDs > 2, don't */
            /* do anything - all handles in the stdio buffer are initialized with */
            /* INVALID_HANDLE_VALUE, which should be okay. */
            if (i <= 2) {
                DWORD access = (i == 0) ? FILE_GENERIC_READ : FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES;

                err = uv__create_nul_handle(&CHILD_STDIO_HANDLE(buffer, i),
                    access);
                if (err)
                    goto error;

                CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FDEV;
            }
            break;

        case UV_CREATE_PIPE: {
            /* Create a pair of two connected pipe ends; one end is turned into */
            /* an uv_pipe_t for use by the parent. The other one is given to */
            /* the child. */
            uv_pipe_t* parent_pipe = (uv_pipe_t*)fdopt.data.stream;
            HANDLE child_pipe = INVALID_HANDLE_VALUE;

            /* Create a new, connected pipe pair. stdio[i].stream should point */
            /* to an uninitialized, but not connected pipe handle. */
            assert(fdopt.data.stream->type == UV_NAMED_PIPE);
            assert(!(fdopt.data.stream->flags & UV_HANDLE_CONNECTION));
            assert(!(fdopt.data.stream->flags & UV_HANDLE_PIPESERVER));

            err = uv__create_stdio_pipe_pair(loop,
                parent_pipe,
                &child_pipe,
                fdopt.flags);
            if (err)
                goto error;

            CHILD_STDIO_HANDLE(buffer, i) = child_pipe;
            CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FPIPE;
            break;
        }

        case UV_INHERIT_FD: {
            /* Inherit a raw FD. */
            HANDLE child_handle;

            /* Make an inheritable duplicate of the handle. */
            err = uv__duplicate_fd(loop, fdopt.data.fd, &child_handle);
            if (err) {
                /* If fdopt.data.fd is not valid and fd fd <= 2, then ignore the */
                /* error. */
                if (fdopt.data.fd <= 2 && err == ERROR_INVALID_HANDLE) {
                    CHILD_STDIO_CRT_FLAGS(buffer, i) = 0;
                    CHILD_STDIO_HANDLE(buffer, i) = INVALID_HANDLE_VALUE;
                    break;
                }
                goto error;
            }

            /* Figure out what the type is. */
            switch (GetFileType(child_handle)) {
            case FILE_TYPE_DISK:
                CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN;
                break;

            case FILE_TYPE_PIPE:
                CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FPIPE;

            case FILE_TYPE_CHAR:
            case FILE_TYPE_REMOTE:
                CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FDEV;
                break;

            case FILE_TYPE_UNKNOWN:
                if (GetLastError() != 0) {
                    err = GetLastError();
                    CloseHandle(child_handle);
                    goto error;
                }
                CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FDEV;
                break;

            default:
                assert(0);
                return -1;
            }

            CHILD_STDIO_HANDLE(buffer, i) = child_handle;
            break;
        }

        case UV_INHERIT_STREAM: {
            /* Use an existing stream as the stdio handle for the child. */
            HANDLE stream_handle, child_handle;
            unsigned char crt_flags;
            uv_stream_t* stream = fdopt.data.stream;

            /* Leech the handle out of the stream. */
            if (stream->type == UV_TTY) {
                stream_handle = ((uv_tty_t*)stream)->handle;
                crt_flags = FOPEN | FDEV;
            } else if (stream->type == UV_NAMED_PIPE && stream->flags & UV_HANDLE_CONNECTION) {
                stream_handle = ((uv_pipe_t*)stream)->handle;
                crt_flags = FOPEN | FPIPE;
            } else {
                stream_handle = INVALID_HANDLE_VALUE;
                crt_flags = 0;
            }

            if (stream_handle == NULL || stream_handle == INVALID_HANDLE_VALUE) {
                /* The handle is already closed, or not yet created, or the */
                /* stream type is not supported. */
                err = ERROR_NOT_SUPPORTED;
                goto error;
            }

            /* Make an inheritable copy of the handle. */
            err = uv__duplicate_handle(loop, stream_handle, &child_handle);
            if (err)
                goto error;

            CHILD_STDIO_HANDLE(buffer, i) = child_handle;
            CHILD_STDIO_CRT_FLAGS(buffer, i) = crt_flags;
            break;
        }

        default:
            assert(0);
            return -1;
        }
    }

    *buffer_ptr = buffer;
    return 0;

error:
    uv__stdio_destroy(buffer);
    return err;
}

void uv__stdio_destroy(BYTE* buffer)
{
    int i, count;

    count = CHILD_STDIO_COUNT(buffer);
    for (i = 0; i < count; i++) {
        HANDLE handle = CHILD_STDIO_HANDLE(buffer, i);
        if (handle != INVALID_HANDLE_VALUE) {
            CloseHandle(handle);
        }
    }

    uv__free(buffer);
}

void uv__stdio_noinherit(BYTE* buffer)
{
    int i, count;

    count = CHILD_STDIO_COUNT(buffer);
    for (i = 0; i < count; i++) {
        HANDLE handle = CHILD_STDIO_HANDLE(buffer, i);
        if (handle != INVALID_HANDLE_VALUE) {
            SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
        }
    }
}

int uv__stdio_verify(BYTE* buffer, WORD size)
{
    unsigned int count;

    /* Check the buffer pointer. */
    if (buffer == NULL)
        return 0;

    /* Verify that the buffer is at least big enough to hold the count. */
    if (size < CHILD_STDIO_SIZE(0))
        return 0;

    /* Verify if the count is within range. */
    count = CHILD_STDIO_COUNT(buffer);
    if (count > 256)
        return 0;

    /* Verify that the buffer size is big enough to hold info for N FDs. */
    if (size < CHILD_STDIO_SIZE(count))
        return 0;

    return 1;
}

WORD uv__stdio_size(BYTE* buffer)
{
    return (WORD)CHILD_STDIO_SIZE(CHILD_STDIO_COUNT((buffer)));
}

HANDLE uv__stdio_handle(BYTE* buffer, int fd)
{
    return CHILD_STDIO_HANDLE(buffer, fd);
}
